package uk.ac.ebi.dod.utils;

import uk.ac.ebi.dod.beans.Enzyme;
import uk.ac.ebi.dod.beans.Filter;

import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.io.File;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Florian Reisinger
 * @since 0.1
 */
public class ScriptUtil {

    private static final Log log = LogFactory.getLog(ScriptUtil.class);

    // ToDo: document! JavaDoc
    // ToDo: change to checked exceptions
    // ToDo: cmd generation now only for needed parameter (extend to make more flexible)
    // ToDo: load properties from file ? (lowest priority)

    ///// ///// ///// ///// /////
    // CONSTANTS

    // values needed to create the unique ID
    private static final String ALLOWED_CHARS = "abcdefghijklmonpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static Random r = new Random();

    // if set to 'true', it will not execute the workflow, only write it to the temporary work directory
    // in addition the base directories will change from the production directories to project relative local directories 
    private static final boolean TEST_RUN = false;

    public static final String BASE_DIR_RESULT;
    public static final String BASE_DIR_TMP;
    public static final String BASE_DIR_TMP_WORK;
    public static final String BASE_DIR_BIN;

    static {
        if (TEST_RUN) {
            // directories for local testing
            BASE_DIR_RESULT   = "dod-work" + File.separator + "databases";
            BASE_DIR_TMP      = "dod-work" + File.separator + "temp";
            BASE_DIR_BIN      = "dod-work" + File.separator + "bin";
            BASE_DIR_TMP_WORK = "dod-work" + File.separator + "temp_nobackup";
        } else {
            // directories for /ebi/production  (temporary until /nobackup is abvailable on the web server)
            BASE_DIR_RESULT   = "/ebi/production/seqdb/sp/database-on-demand/databases";
            BASE_DIR_TMP      = "/ebi/production/seqdb/sp/database-on-demand/temp";
            BASE_DIR_BIN      = "/ebi/production/seqdb/sp/database-on-demand/bin";
            BASE_DIR_TMP_WORK = "/nobackup/vol1/pride-dod/temp";
            // directories for /nobackup  (should be production code)
//            BASE_DIR_RESULT = "/nobackup/vol1/pride-dod/databases";
//            BASE_DIR_TMP    = "/nobackup/vol1/pride-dod/temp";
//            BASE_DIR_BIN    = "/nobackup/vol1/pride-dod/bin";
        }
    }

    // ToDo: could make these public
    // The DOD_HOME and DOD_CLASSPATH env variables are set in the 'shell-setuprc' script in BASE_DIR_BIN defined above.
    private static final String CMD_PREFIX = "java -Xmx6g -cp ${DOD_HOME}:${DOD_CLASSPATH} be.proteomics.dbtoolkit.toolkit.";
    private static final String DOWNLOAD_URL_REFIX = "http://www.ebi.ac.uk/pride/dod/databases/";
    private static final String ADMIN_EMAIL = "pride-dod@ebi.ac.uk";
    private static final String REPORT_FILE = "workflow.txt";
    private static final String ENZYME_FILE = "enzymes.txt";
    private static final String LSFEXE_FILE = "submitLSFJobDOD.sh";
    private static final String SOURCE_FILE = "shell-setuprc";
    private static final String FAILURE_HEADER = "failure.txt";
    private static final String SUCCESS_HEADER = "success.txt";
    private static final String INDENT = "  ";

    // ToDo: these should not be defined here
    // ToDo: these should not be used any more
    // ToDo: maturation currently is only EXCL_PROPEP!! no need for different values.
    public static final int PROPEP = 0;
    public static final int EXCL_PROPEP = 1;


    ///// ///// ///// ///// /////
    // INSTANCE VARIABLES

    public final String uniqueID;   // used to create a temp folder for intermediate results and the final result file name
    private final String tmpPrefix; // the prefix for files in the temporary working directory (composed of the BASE_DIR_TMP and the uniqueID
    private final String tmpWorkPrefix; // the prefix for files on the actual work directory (nobackup)
    private final String resPrefix; // the prefix for files in the result directory
    private final String resultFileName;
    private final String gzipResultFileName; // the name that the result file will have (ends in '.gz' since it will be compressed)
    private final String scriptFile;


    ///// ///// ///// ///// /////
    // CONSTRUCTOR

    public ScriptUtil() {
        log.info("Creating new ScriptUtil.");
        uniqueID = generateUniqueString();
        tmpPrefix = BASE_DIR_TMP + File.separator + uniqueID + File.separator;
        tmpWorkPrefix = BASE_DIR_TMP_WORK + File.separator + uniqueID + File.separator;
        resPrefix = BASE_DIR_RESULT + File.separator;
        resultFileName = uniqueID + ".fasta";
        gzipResultFileName = resultFileName + ".gz";
        scriptFile = uniqueID + ".sh";
    }


    ///// ///// ///// ///// /////
    // METHODS

    /**
     * Attempt to create a directory on the filesystem for job specific content.
     * Note: currenttly two filesystem locations are in use: production and nobackup.
     *       Only the production FS can we accessed from this code. The nobackup is
     *       handled by the execution script.
     *
     * @return true if the temporary working directory was successfully created.
     */
    public boolean createTempJobFolder() {
        log.info("Creating temporary work directory " + tmpPrefix);
        File tmp = new File(tmpPrefix);
        boolean tmpOK = tmp.mkdir();
        boolean workOK = true;
        // return true only if both directories could be created
        return (tmpOK && workOK);
    }

    public int checkFS(boolean jobFolderCreated) {
        log.debug("Checking the filesystem for expected layout.");
        // check if the result directory exists
        File test = new File(BASE_DIR_RESULT);
        if (!test.exists()) {
            return -1;
        }

        // check if the general working directory exists
        test = new File(BASE_DIR_TMP);
        if (!test.exists()) {
            return -2;
        }

        if (!jobFolderCreated) {
            // check if temporary working directory for the current job already exists
            // it should NOT exist! (the flag was set accordingly)
            test = new File(tmpPrefix);
            if (test.exists()) {
                return -3;
            }
        } else {
            // check if temporary working directory for the current job already exists
            // it should exist! (the flag was set accordingly)
            test = new File(tmpPrefix);
            if (!test.exists()) {
                return -4;
            }
        }

        // now we know that the needed directories exist
        // ToDo: ? do additional checking (e.g. can read/write) ?

        return 0;
    }

    public String getScriptHeader() {
        StringBuilder sb = new StringBuilder();

        // include the shell script header
        sb.append("#!/bin/bash\n");
        // include the setup sources (which define the environment variables)
        sb.append("source ").append(BASE_DIR_BIN).append(File.separator).append(SOURCE_FILE);
        sb.append("\n");

        return sb.toString();
    }

    public String buildTmpDirCmd() {
        StringBuilder sb = new StringBuilder();
        // create the nobackup folder first
        sb.append("mkdir ").append(tmpWorkPrefix);
        sb.append("\n");
        return sb.toString();
    }

    public String copyToTempDestCmd(String inputDBSource, String inputDBName) {
        StringBuilder sb = new StringBuilder();

        // copy the input source from its original location to the temporary working directory (nobackup)
        sb.append("cp ").append(inputDBSource).append(" ").append(tmpWorkPrefix).append(inputDBName);
        sb.append("\n");

        return sb.toString();
    }

    public String buildFilterSetParam(List<Filter> filters) {
        StringBuilder sb = new StringBuilder();
        boolean firstOne = true;
        for (Filter filter : filters) {
            if (filter.getName().equalsIgnoreCase("Maturation")) {
                // in this case we use a completely different command line,
                // we do NOT add this filter to the filterSet
            } else {
                // See if we need to prefix with ';' (which is in all cases except for the first.
                if(!firstOne) {
                    sb.append(";");
                } else {
                    firstOne = false;
                }
                if (filter.getEnableParamInput() && filter.getParameter() != null && filter.getParameter().length() > 0) {
                    // add a entry for the filter with parameter support: <filterName>=<filterParam>
                    sb.append(filter.getName()).append("=").append(filter.getParameter());
                } else {
                    // add a entry for the filter without parameter support: <filterName>
                    sb.append(filter.getName());
                }
            }
        }

        return sb.toString();
    }

    public String buildMaturationCmd(String filterSetParam, String inDB, String outDB) {
        // ProteinMaturationDevice [--filterSet "<filter1_name=filter1_param;filter2_name;filter3_name=filter3_param;...>"]" +
        //                                                       " --mode <CHAIN|PROPEP|EXCL_PROPEP>
        //                                                       " [-c] [-m]
        //                                                       " <swissprot_formatted_input_database>
        //                                                       " <output_file>
        if (filterSetParam == null || inDB == null || outDB == null) {
            throw new IllegalArgumentException("All arguments (filterSetParam, inDB, outDB) need to be specified to build a correct maturation filter command!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("ProteinMaturationDevice");
        // we know that we have a filterSetParam (otherwise IllegalArgumentException above)
        // but we still have to check if is actually something in it (and not just a empty String))
        if ( filterSetParam.length() > 0 ) {
            sb.append(" --filterSet \"").append(filterSetParam).append("\"");
        }
        sb.append(" --mode EXCL_PROPEP");
        sb.append(" ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildFilterCmd(String filterSetParam, String inDB, String outDB) {
        // FASTAOutput [--filterSet <filterSetParam>]
        //             [--filter <filter_name> [--filterParam "<filter_parameter>"]]
        //             [--lowMass <lower_mass_treshold> --highMass <higher_mass_treshold>]
        //             --input <input_file_name>
        //             <output_db_name>
        if (filterSetParam == null || inDB == null || outDB == null) {
            throw new IllegalArgumentException("All arguments (filterSetParam, inDB, outDB) need to be specified to build a correct filter command!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("FASTAOutput");
        sb.append(" --filterSet \"").append(filterSetParam).append("\"");
        sb.append(" --input ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildFastaOutputCmd(String inDB, String outDB) {
        // FASTAOutput [--filterSet <filterSetParam>]
        //             [--filter <filter_name> [--filterParam "<filter_parameter>"]]
        //             [--lowMass <lower_mass_treshold> --highMass <higher_mass_treshold>]
        //             --input <input_file_name>
        //             <output_db_name>
        if (inDB == null || outDB == null) {
            throw new IllegalArgumentException("All arguments (inDB, outDB) need to be specified to build a correct FASTAOutput command!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("FASTAOutput");
        sb.append(" --input ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildMassFilterCmd(int lowMass, int highMass, String inDB, String outDB) {
        // FASTAOutput [--filter <filter_name> [--filterParam "<filter_parameter>"]]
        //             [--lowMass <lower_mass_treshold> --highMass <higher_mass_treshold>]
        //             --input <input_file_name>
        //             <output_db_name>
        if (lowMass < 1 || highMass < 1 || inDB == null || outDB == null) {
            throw new IllegalArgumentException("All arguments (lowMass, highMass, inDB, outDB) need to be specified! The masses must be greateer than 0!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("FASTAOutput");
        if (lowMass > 0 && highMass > 0 && highMass > lowMass) {
            sb.append(" --lowMass ").append(lowMass);
            sb.append(" --highMass ").append(highMass);
        }
        sb.append(" --input ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildCatCmd(String inDB, String outDB) {
        // cat file1 >> file2  (to combine the databases)
        if (inDB == null || outDB == null) {
            throw new IllegalArgumentException("Both arguments (inDB, outDB) have to be specified!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append("cat ").append(tmpWorkPrefix).append(inDB).append(" >> ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildDigestCmd(String enzymeName, int mc, int lowMass, int highMass, String inDB, String outDB) {
        // EnzymeDigest [--enzyme <enzymeName> [--mc <number_of_missed_cleavages>]]
        //              [--filter <filter_name> [--filterParam "<filter_parameter>"]]
        //              [--lowMass <lower_mass_treshold> --highMass <higher_mass_treshold>]
        //              --input <input_db_name>
        //              <output_db_name>
        if (enzymeName == null || mc < 0 || inDB == null || outDB == null) {
            throw new IllegalArgumentException("The enzyme and input/output DBs need to be specified and the number of missed cleavages needs to be positive!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("EnzymeDigest");
        sb.append(" --enzymeFile ").append(tmpPrefix).append(ENZYME_FILE);
        sb.append(" --enzyme ").append(enzymeName);
        sb.append(" --mc ").append(mc);
        if (lowMass > 0 && highMass > 0 && highMass > lowMass) {
            sb.append(" --lowMass ").append(lowMass);
            sb.append(" --highMass ").append(highMass);
        }
        sb.append(" --input ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildPMDCmd(int mode, String inDB, String outDB) {
        // ProteinMaturationDevice [--taxID <taxonomy_id>]
                                   // --mode <CHAIN|PROPEP|EXCL_PROPEP>
                                   // [-c]
                                   // [-m]
                                   // <swissprot_formatted_input_database>
                                   // <output_file>
        if (mode < 0 || inDB == null || outDB == null) {
            throw new IllegalArgumentException("Input/output DB locations and maturation mode have to be specified!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("ProteinMaturationDevice");
        sb.append(" --mode ").append(mode == PROPEP ? "PROPEP" : "EXCL_PROPEP");
        sb.append(" ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildRagCmd(int ragMethod, int truncLimit, int lowMass, int highMass, String inDB, String outDB) {
        // RagDB -(N|C)
        //       [--enzyme <enzymeName> [--mc <number_of_missed_cleavages>]]
        //       [--filter <filter_name> [--filterParam "<filter_parameter>"]]
        //       [--truncate <number_of_residues>]
        //       [--lowMass <lower_mass_treshold> --highMass <higher_mass_treshold>]
        //       --input <input_db_name>
        //       <output_db_name>
        if (inDB == null || outDB == null) {
            throw new IllegalArgumentException("Input and output DB locations have to be specified!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("RagDB -").append(ragMethod == 0 ? "C" : "N");
        if (truncLimit > 0) {
            sb.append(" --truncate ").append(truncLimit);
        }
        if (lowMass > 0 && highMass > 0 && highMass > lowMass) {
            sb.append(" --lowMass ").append(lowMass);
            sb.append(" --highMass ").append(highMass);
        }
        sb.append(" --input ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildSeqFilterCmd(String query, int lowMass, int highMass, String inDB, String outDB) {
        // IsolateSubset [--enzyme <enzymeName> [--mc <number_of_missed_cleavages>]]
        //               [--filter <filter_name> [--filterParam \"<filter_parameter>\"]]
        //               [--lowMass <lower_mass_treshold> --highMass <higher_mass_treshold>]
        //               --query "<query_string>"
        //               --input <input_db_name>
        //               <output_db_name>;
        if (query == null || inDB == null || outDB == null) {
            throw new IllegalArgumentException("All the parameter (query, inDB, outDB) need to be specified!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(CMD_PREFIX).append("IsolateSubset");
        if (lowMass > 0 && highMass > 0 && highMass > lowMass) {
            sb.append(" --lowMass ").append(lowMass);
            sb.append(" --highMass ").append(highMass);
        }
        sb.append(" --query \"").append(query).append("\"");
        sb.append(" --input ").append(tmpWorkPrefix).append(inDB);
        sb.append(" ").append(tmpWorkPrefix).append(outDB);
        sb.append("\n");

        return sb.toString();
    }

    public String buildClearRedundancyCmd(String inDB, String outDB) {
        // ClearRedundancy --temp <tempFolder>
        //                 --output <output_file_name>
        //                 <input_db_name>
        if ( inDB == null || outDB == null ) {
            throw new IllegalArgumentException("Both input and output DBs have to be specified!");
        }

        StringBuilder sb = new StringBuilder();
        sb.append("mkdir ").append(tmpWorkPrefix).append("crTmp");
        sb.append("\n");
        sb.append(CMD_PREFIX).append("ClearRedundancy");
        sb.append(" --temp ").append(tmpWorkPrefix).append("crTmp");
        sb.append(" --output ").append(tmpWorkPrefix).append(outDB);
        sb.append(" ").append(tmpWorkPrefix).append(inDB);
        sb.append("\n");

        return sb.toString();
    }

    public String renameToFinalNameCmd(String resultDB) {
        StringBuilder sb = new StringBuilder();

        sb.append("mv ").append(tmpWorkPrefix).append(resultDB).append(" ").append(tmpWorkPrefix).append(resultFileName);
        sb.append("\n");

        return sb.toString();
    }

    public String buildExitStatusCheckIf() {
        StringBuilder sb = new StringBuilder();

        sb.append("if [ $? -ne 0 ]\n"); // if the previous command did not exit as expected
        sb.append("then\n");
        sb.append("  onFailure\n");     // then we have to assume a error and move on to the failure handling
        sb.append("fi\n");

        return sb.toString();
    }

    public String buildOnFailureProcedure(String emailAddress) {
        StringBuilder sb = new StringBuilder();

        sb.append("onFailure() {\n");
        // send failure emails to the admins and the user
        sb.append(INDENT);
        sb.append("cat ").append(tmpPrefix).append(REPORT_FILE).append(" ").append(tmpPrefix).append(scriptFile);
        sb.append(" | mail -s \"pride-dod job '").append(uniqueID).append("' failed\" ").append(ADMIN_EMAIL);
        sb.append("\n");
        sb.append(INDENT);
        sb.append("cat ").append(BASE_DIR_BIN).append(File.separator).append(FAILURE_HEADER);
        sb.append(" ").append(tmpPrefix).append(REPORT_FILE);
        sb.append(" | mail -s \"your Database On Demand job '").append(uniqueID).append("' failed\" ").append(emailAddress);
        sb.append("\n");
        // abort the job (all remaining steps would not make sense anyway)
        sb.append(INDENT);
        sb.append("exit -1\n");
        sb.append("}\n");

        return sb.toString();
    }

    public String buildOnSuccessProcedure(String emailAddress) {
        StringBuilder sb = new StringBuilder();

        sb.append("onSuccess() {\n");

        // gzip the database to reduce space and download time
        sb.append(INDENT);
        sb.append("gzip ").append(tmpWorkPrefix).append(resultFileName); // !! will result in a file name change !! -> gzipResultFileName
        sb.append("\n");
        // careful!! the name will have changed with this step!! ending '.gz' added.
        // we have to use gzipResultFileName from now on

        // move (instead of copying to (potentially) save time) to final destination
        sb.append(INDENT);
        sb.append("mv ").append(tmpWorkPrefix).append(gzipResultFileName).append(" ").append(resPrefix).append(gzipResultFileName);
        sb.append("\n");

        // wait for the filesystem
        sb.append(INDENT);
        sb.append("sleep 60\n");
        // send confirmation emails to the admins and the user
        sb.append(INDENT);
        sb.append("cat ").append(tmpPrefix).append(REPORT_FILE).append(" ").append(tmpPrefix).append(scriptFile);
        sb.append(" | mail -s \"pride-dod job '").append(uniqueID).append("' completed\" ").append(ADMIN_EMAIL);
        sb.append("\n");
        sb.append(INDENT);
        sb.append("cat ").append(tmpPrefix).append(SUCCESS_HEADER).append(" ").append(tmpPrefix).append(REPORT_FILE);
        sb.append(" | mail -s \"your Database On Demand is ready for download\" ").append(emailAddress);
        sb.append("\n");

        // finally clean up the temporary working directory (on production AND nobackup)
        sb.append(INDENT);
        sb.append("rm -rf ").append(tmpPrefix).append("*\n");
        sb.append(INDENT);
        sb.append("rm -rf ").append(tmpWorkPrefix).append("*\n");

        sb.append("}\n");

        return sb.toString();
    }

    public String buildSuccessCmd() {
        StringBuilder sb = new StringBuilder();

        sb.append("\n");          // add a empty line for readability
        sb.append("onSuccess\n"); // call the onSuccess sub-routine
        sb.append("exit 0\n");    // exit clean with value 0

        return sb.toString();
    }

    public String sendConfirmEmailCmd(String emailAddress) {
        return "echo \"Your Database On Demand is being generated. Your unique ID for this " +
                "task is " + uniqueID + ". Please use this unique ID as a reference if you " +
                "should need to contact us about any problems at pride-dod@ebi.ac.uk.\" |  " +
                "mail -s \"your Database On Demand is being generated\" " + emailAddress + "\n";
    }

    public void writeSuccessHeaderToTmpDest(String additionalContent) throws IOException {
        StringBuilder sb = new StringBuilder();

        sb.append("Your custom Database On Demand is now available.\n");
        sb.append("Please note that we will keep the data for a maximum of 30 days.\n\n");
        sb.append("You can use the following URL to download your database:\n\n");
        sb.append(DOWNLOAD_URL_REFIX).append(gzipResultFileName).append("\n\n");
        sb.append("\n\n\n");
        if (additionalContent != null) {
            sb.append(additionalContent).append("\n\n\n");
        }

        writeFileToTmpDest(SUCCESS_HEADER, sb.toString());
    }
    
    public void writeReportToTmpDest(String report) throws IOException {
        StringBuilder sb = new StringBuilder();
        sb.append("\nYou requested a Database On Demand according to the following workflow:\n\n");
        sb.append(report);
        sb.append("\n\n\n");
        writeFileToTmpDest(REPORT_FILE, sb.toString());
    }

    public void writeEnzymesFileToTmpDest(List<Enzyme> enzymes) throws IOException {
        if (enzymes != null && enzymes.size() > 0) {
            String enzymeTxt = enzymesToString(enzymes);
            writeFileToTmpDest(ENZYME_FILE, enzymeTxt);
        }
    }

    public void writeScriptToTmpDest(String script) throws IOException {
        // ToDo: ? keep throwing the IOExceptions or return status (boolean) ?
        writeFileToTmpDest(scriptFile, script);
    }

    public void executeWorkflow() {
        if (TEST_RUN) {
            log.debug("NOT executing since this is only a test run!");
        } else {
            log.info("Executing workflow with ID: " + uniqueID);

            ArrayList<String> process_name = new ArrayList<String>();
            process_name.add(BASE_DIR_BIN + File.separator + LSFEXE_FILE);
            process_name.add(tmpPrefix + scriptFile);

            ProcessBuilder pb = new ProcessBuilder(process_name);
            // Set execution path (cd to this dir before executing)
            // pb.directory(dbFilePath);

            try {
                Process p = pb.start();
                // ToDo: check why that does not work!
//                int exitValue = p.exitValue();
//            int resultInt = p.waitFor();
//            System.out.println("bsub returned: " + resultInt);
            } catch (IOException ioe) {
                log.error("Exception trying to start external process to execute script!", ioe);
                ioe.printStackTrace();
            }
//        catch(InterruptedException ie) {
//            // Interrupted.
//        }
            log.info("Workflow submitted to LSF cluster! Execution of workflow started.");
        }
    }

    ///// ///// ///// ///// /////
    // PRIVATE UTILITY METHODS

    private String enzymesToString(List<Enzyme> enzymes) {
        StringBuilder sb = new StringBuilder();

        // for each enzyme in the list create a text block in the format dbtoolkit can read
        for (Enzyme enzyme : enzymes) {
            String enzName;
            if (enzyme.getType() == 0) {
                // normal enzyme
                enzName = enzyme.getName();
            } else if (enzyme.getType() == 1) {
                enzName = "regex" + enzyme.getName();
            } else {
                throw new IllegalStateException("A Enzyme is not supposed to have a type different from 0 or 1!");
            }
            // Title:
            sb.append("Title:").append(enzName).append("\n");
            // Cleavage:
            sb.append("Cleavage:").append(enzyme.getCleavePattern()).append("\n");
            // Restrict:
            sb.append("Restrict:").append(enzyme.getRestrictors()).append("\n");
            // Cterm/Nterm
            sb.append(enzyme.getCNTermAsString()).append("\n");
            // *
            sb.append("*\n");
        }

        return sb.toString();
    }

    private void writeFileToTmpDest(String fileName, String content) throws IOException {
        File file = new File(tmpPrefix + fileName);
        // ToDo: do some checking: if already exists, if can create/write, ...
        BufferedWriter writer = new BufferedWriter(new FileWriter(file));
        writer.write(content);
        writer.flush();
        writer.close();
    }

    /**
     * Generates a unique string using the current system time plus a random string.
     * Format: "current system time in ms"_"random string of length 5"
     *
     * @return the generated unique string.
     */
    private String generateUniqueString() {
        // the system time in ms is fairly unique
        long time = System.currentTimeMillis();
        // but this method alone can be called ~100 times in one ms, so we need another factor
        // generate a unique random String to be appended to the time, just in case
        return time + "_" + generateRandomString(5);
    }

    /**
     * Generate a random String.
     * This method creates a random sequence of characters (from the lilst of allowed
     * characters) with the specified length.
     * Note: Allowed characters are: a-zA-Z0-9
     *
     * @param length the length the unique string should have.
     * @return a unique string with the specified length.
     */
    private synchronized String generateRandomString(int length) {
        char[] buf = new char[length];
        for (int i=0; i<length; i++) {
            buf[i] = ALLOWED_CHARS.charAt(r.nextInt(ALLOWED_CHARS.length()));
        }
        return new String(buf);
    }
}
